import 'dart:convert';
import 'dart:typed_data';
import 'dart:js' as js;

import 'dart:js_interop';
import '../common.dart';
import 'sxtwlJS.dart';
import 'sxtwlWasm.dart';
import 'package:archive/archive_io.dart';
import 'package:web/web.dart';
import 'dart:async';

Future<bool> init() async {
  if (document.body?.querySelector(".sxtwl_js") != null) {
    return true;
  }

  final Completer<bool> completer = Completer<bool>();

  var byteData = getSxtwlasmByteArray();
  byteData = Uint8List.fromList(GZipDecoder().decodeBytes(byteData));

  js.context['getSxtwlasmByteArray'] = () {
    return byteData.toJS;
  };

  js.context['sxtwlWasmFinish'] = () {
    completer.complete(true);
  };

  var script = document.createElement('script') as HTMLScriptElement;
  script.type = 'text/javascript';
  script.className = "sxtwl_js";
  script.textContent = utf8.decode(
      Uint8List.fromList(GZipDecoder().decodeBytes(getSxtwlJSByteArray())));

  //script.textContent = utf8.decode(base64Decode(sxtwlJs));
  document.body?.appendChild(script);

  return await completer.future;
}

class GZ {
  dynamic ptr;

  GZ({int tg = 0, int dz = 0}) {
    ptr = js.context.callMethod("_GZ_new", [tg, dz]);
  }

  int get dz {
    return js.context.callMethod("_GZ_getDz", [ptr]);
  }

  int get tg {
    return js.context.callMethod("_GZ_getTg", [ptr]);
  }

  set dz(int value) {
    js.context.callMethod("_GZ_setDz", [ptr, value]);
  }

  set tg(int value) {
    js.context.callMethod("_GZ_setTg", [ptr, value]);
  }
}

class Day {
  late dynamic _dayPtr;

  Day._fromDayPtr(dynamic dayPtr) {
    _dayPtr = dayPtr;
  }

  Day.fromSolar({required int year, required int month, required int day}) {
    _dayPtr = js.context.callMethod("_sxtwl_newDay");
    js.context.callMethod("_sxtwl_fromSolar", [_dayPtr, year, month, day]);
  }

  Day.fromLunar(
      {required int year,
      required int month,
      required int day,
      bool isRun = false}) {
    _dayPtr = js.context.callMethod("_sxtwl_newDay");
    js.context
        .callMethod("_sxtwl_fromLunar", [_dayPtr, year, month, day, isRun]);
  }

  Day after({required int dayNum}) {
    var outDayPtr = js.context.callMethod("_sxtwl_newDay");
    js.context.callMethod("_sxtwl_after", [outDayPtr, outDayPtr, dayNum]);
    return Day._fromDayPtr(outDayPtr);
  }

  Day before({required int dayNum}) {
    var outDayPtr = js.context.callMethod("_sxtwl_newDay");
    js.context.callMethod("_sxtwl_before", [outDayPtr, outDayPtr, dayNum]);
    return Day._fromDayPtr(outDayPtr);
  }

  int getLunarYear({bool chineseNewYearBoundary = false}) {
    return js.context
        .callMethod("_sxtwl_getLunarYear", [_dayPtr, chineseNewYearBoundary]);
  }

  int getLunarMonth() {
    return js.context.callMethod("_sxtwl_getLunarMonth", [_dayPtr]);
  }

  int getLunarDay() {
    return js.context.callMethod("_sxtwl_getLunarDay", [_dayPtr]);
  }

  int getSolarYear() {
    return js.context.callMethod("_sxtwl_getSolarYear", [_dayPtr]);
  }

  int getSolarMonth() {
    return js.context.callMethod("_sxtwl_getSolarMonth", [_dayPtr]);
  }

  int getSolarDay() {
    return js.context.callMethod("_sxtwl_getSolarDay", [_dayPtr]);
  }

  GZ getYearGZ({bool chineseNewYearBoundary = false}) {
    var ret = GZ();
    js.context.callMethod(
        "_sxtwl_getYearGZ", [ret.ptr, _dayPtr, chineseNewYearBoundary]);

    return ret;
  }

  GZ getMonthGZ() {
    var ret = GZ();
    js.context.callMethod("_sxtwl_getMonthGZ", [ret.ptr, _dayPtr]);

    return ret;
  }

  GZ getDayGZ() {
    var ret = GZ();
    js.context.callMethod("_sxtwl_getDayGZ", [ret.ptr, _dayPtr]);

    return ret;
  }

  GZ getHourGZ({required int hour, bool isZaoWanZiShi = true}) {
    var ret = GZ();
    js.context.callMethod("_sxtwl_getHourGZ", [ret.ptr, _dayPtr]);

    return ret;
  }

  bool isLunarLeap() {
    return js.context.callMethod("_sxtwl_isLunarLeap", [_dayPtr]);
  }

  // 星期几
  int getWeek() {
    return js.context.callMethod("_sxtwl_getWeek", [_dayPtr]);
  }

  // 处于该月的第几周
  int getWeekIndex() {
    return js.context.callMethod("_sxtwl_getWeekIndex", [_dayPtr]);
  }

  // 获取星座
  int getConstellation() {
    return js.context.callMethod("_sxtwl_getConstellation", [_dayPtr]);
  }

  // 是否有节气
  bool hasJieQi() {
    return js.context.callMethod("_sxtwl_hasJieQi", [_dayPtr]);
  }

  // 获取节气索引
  int getJieQi() {
    return js.context.callMethod("_sxtwl_getJieQi", [_dayPtr]);
  }

  double getJieQiJD() {
    return js.context.callMethod("_sxtwl_getJieQiJD", [_dayPtr]);
  }

  dispose() {
    return js.context.callMethod("_sxtwl_freeDay", [_dayPtr]);
  }
}

//========================================================================================
JdList siZhu2Year(
    {required int yearTg,
    required int yearDz,
    required int monthTg,
    required int monthDz,
    required int dayTg,
    required int dayDz,
    required int hourTg,
    required int hourDz,
    int fromYear = 1800,
    int toYear = 2300}) {
  var jdListPtr = js.context.callMethod("_JdList_new", []);

  return JdList().init(js.context.callMethod("_sxtwl_siZhu2Year", [
    jdListPtr,
    GZ(tg: yearTg, dz: yearDz).ptr,
    GZ(tg: monthTg, dz: monthDz).ptr,
    GZ(tg: dayTg, dz: dayDz).ptr,
    GZ(tg: hourTg, dz: hourDz).ptr,
    fromYear,
    toYear
  ]));
}

GZ getShiGz(
    {required int dayTg, required int hour, bool isZaoWanZiShi = true}) {
  var ret = GZ();
  js.context
      .callMethod("_sxtwl_getShiGz", [ret.ptr, dayTg, hour, isZaoWanZiShi]);

  return ret;
}

// 获取一年中的阴历润月
getRunMonth({required int year}) {
  return js.context.callMethod("_sxtwl_getRunMonth", [year]);
}

// 获取阴历月的天数
getLunarMonthNum({required int year, required int month, bool isRun = false}) {
  return js.context.callMethod("_sxtwl_getLunarMonthNum", [year, month, isRun]);
}

int getSolarMonthDayNum({
  required int year,
  required int month,
}) {
  var MONTH_DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

  if (month == 2) {
    var isLeapYear = false;

    if (year % 100 != 0) {
      isLeapYear = ((year % 4) == 0);
    } else {
      isLeapYear = ((year % 400) == 0);
    }

    if (isLeapYear) {
      MONTH_DAYS[1] = 29;
    } else {
      MONTH_DAYS[1] = 28;
    }
  }

  return MONTH_DAYS[month];
}

SxtwlTime JD2DD({required double jd}) {
  var time = SxtwlTime();
  js.context.callMethod("_sxtwl_JD2DD", [time.ptr, jd]);

  return time;
}

double toJD(SxtwlTime time) {
  return js.context.callMethod("_sxtwl_toJD", [time.ptr]);
}

//========================================================================================
class JdList {
  late dynamic ptr;

  // static final _finalizer = ffi.NativeFinalizer(_dylib.lookup('JdList_new'));

  JdList init(dynamic ptr) {
    ptr = js.context.callMethod("_JdList_new", [ptr]);

    return this;
  }

  int get num {
    return js.context.callMethod("_JdList_getNum", [ptr]);
  }

  double indexOf(int index) {
    return js.context.callMethod("_JdList_indexOf", [ptr, index]);
  }
}

class SxtwlTime {
  late dynamic ptr;

  SxtwlTime(
      {int year = 0,
      int month = 0,
      int day = 0,
      double hour = 0,
      double min = 0,
      double sec = 0}) {
    ptr = js.context.callMethod("_Time_new");

    this.year = year;
    this.month = month;
    this.day = day;
    this.hour = hour;
    this.min = min;
    this.sec = sec;
  }

  int get year {
    return js.context.callMethod("_Time_getYear", [ptr]);
  }

  set year(int value) {
    js.context.callMethod("_Time_setYear", [ptr, value]);
  }

  int get month {
    return js.context.callMethod("_Time_getMonth", [ptr]);
  }

  set month(int value) {
    js.context.callMethod("_Time_setMonth", [ptr, value]);
  }

  int get day {
    return js.context.callMethod("_Time_getDay", [ptr]);
  }

  set day(int value) {
    js.context.callMethod("_Time_setDay", [ptr, value]);
  }

  double get hour {
    return js.context.callMethod("_Time_getHour", [ptr]);
  }

  set hour(double value) {
    js.context.callMethod("_Time_setHour", [ptr, value]);
  }

  double get min {
    return js.context.callMethod("_Time_getMin", [ptr]);
  }

  set min(double value) {
    js.context.callMethod("_Time_setMin", [ptr, value]);
  }

  double get sec {
    return js.context.callMethod("_Time_getSec", [ptr]);
  }

  set sec(double value) {
    js.context.callMethod("_Time_setSec", [ptr, value]);
  }
}
